# Copyright (c) 2019 Presto Labs Pte. Ltd.
# Author: xguo

import datetime
import itertools
import json
import logging

from tabulate import tabulate

from coin.base.book.book_builder_base import BookBuilderBase, ThinBookError
from coin.base.book.types import MarketTradeEvent, TradeSide


def human_readable_timestamp_record(timestamp):
  ts = timestamp / 1e9
  hr_ts = datetime.datetime.fromtimestamp(ts).strftime('%Y%m%d %H:%M:%S.%f')
  record = {
      'timestamp': timestamp,
      'timestamp_human_readable': hr_ts,
  }
  return record


def _convert_item(value):
  if value is None:
    return [None, None]
  else:
    return value[1].price, value[1].qty


def pretty_book(book: BookBuilderBase, level=10):
  fields = ['level', 'bid price', 'bid qty', 'ask price', 'ask qty']
  items = itertools.zip_longest(book.iteritems_bid(), book.iteritems_ask())
  book_entry_list = []
  for n, item in enumerate(items):
    n += 1
    bid_price, bid_qty = _convert_item(item[0])
    ask_price, ask_qty = _convert_item(item[1])
    entry = [n, bid_price, bid_qty, ask_price, ask_qty]
    book_entry_list.append(entry)
    if level is not None and n >= level:
      break
  return tabulate(book_entry_list, headers=fields)


def convert_book_to_dict(book, level=5, product=None, notional_qty=None, notional_amount=None):
  product = product or getattr(book, 'product', None)
  ask_array = book.get_ask_array(level)
  bid_array = book.get_bid_array(level)
  if not isinstance(ask_array, list):
    ask_array = ask_array.tolist()
    bid_array = bid_array.tolist()
  data = {
      'product': product.full_symbol, 'type': 'book', 'data': {
          'asks': ask_array,
          'bids': bid_array,
      }
  }
  book_info = data['data']
  data.update(human_readable_timestamp_record(book.timestamp))

  try:
    book_info['mid'] = (book_info['asks'][0][0] + book_info['bids'][0][0]) / 2.
  except IndexError:
    book_info['mid'] = None

  if notional_qty is not None:
    try:
      book_info['notional_qty'] = {
          'true_ask': book.get_ask_price_by_qty(notional_qty),
          'true_bid': book.get_bid_price_by_qty(notional_qty),
          'qty': notional_qty,
      }
    except ThinBookError:
      book_info['notional_qty'] = {
          'true_ask': None,
          'true_bid': None,
          'qty': notional_qty,
          'error': 'ThinBookError',
      }
      logging.debug('ThinBookError: %s', book.product)

  if notional_amount is not None:
    try:
      book_info['notional_amount'] = {
          'true_ask': book.get_ask_price_by_amount(notional_amount),
          'true_bid': book.get_bid_price_by_amount(notional_amount),
          'amount': notional_amount,
      }
    except ThinBookError:
      book_info['notional_amount'] = {
          'true_ask': None,
          'true_bid': None,
          'amount': notional_amount,
          'error': 'ThinBookError',
      }
      logging.debug('ThinBookError: %s', book.product)
  return data


def convert_book_to_json(book: BookBuilderBase, level=10, notional_qty=None, notional_amount=None):
  data = convert_book_to_dict(book,
                              level=level,
                              notional_qty=notional_qty,
                              notional_amount=notional_amount)
  json_data = json.dumps(data, indent=2)
  return json_data


def convert_trade_to_dict(product, trade_event: MarketTradeEvent):
  data = {'product': product.full_symbol, 'type': 'trade', 'data': {}}
  # noinspection PyProtectedMember
  json_data = dict(trade_event._asdict())
  data.update(human_readable_timestamp_record(json_data['timestamp']))
  json_data['side'] = TradeSide(json_data['side']).name
  data['data'] = json_data
  return data


def convert_trade_to_json(product, trade_event: MarketTradeEvent):
  data = convert_trade_to_dict(product=product, trade_event=trade_event)
  json_data = json.dumps(data, indent=2)
  return json_data
